import * as THREE from "three"
import { SetMouseCursorCommand, DisableCursorMeshCommand } from "../command/cursor.command"
import CommandBinder from "../CommandBinder"
import configConst from "../constant/config.const"
import cursorEnum from "../enum/cursor.enum"
import pinTypeEnum from "../enum/pinType.enum"
import Animation from "../utils/Animation"
import { HoverEvent, UnhoverEvent } from "../event/GestureEvent"
import PinAnchorMesh from "../mesh/PinAnchorMesh"
import PinSelectedMesh from "../mesh/PinSelectedMesh"
import RoomMesh from "../mesh/RoomMesh"
import InputIniModule from "../module/InputIniModule"
import { matterItemData } from "../module/PinsModule"
import RaycasterModule from "../module/RaycasterModule"
import Comparator from "../utils/Comparator"
import { RenderLayer } from "../utils/RenderLayers"
import { Subscription } from "../utils/Subscription"
import ae from "../math/79437"
import EngineContext from "../EngineContext"
export default class PinRenderer {
  input: InputIniModule
  layer: RenderLayer
  commandBinder: CommandBinder
  raycaster: RaycasterModule
  container: THREE.Object3D
  floorIdToContainer: Map<string, THREE.Object3D>
  hexColorToColor: Map<
    string,
    {
      baseColor: THREE.Color
      hoverColor: THREE.Color
      dimmedColor: THREE.Color
    }
  >
  bindings: Partial<Subscription>[]
  anchor: { pinType: string; id: string }
  selected: {
    pinType: string
    id: string
    hideWhenDoneAnimating: boolean
    animation: Animation
  }
  haloEasing: (e: any, t: any, i: any, n: any) => number
  pinsWithOverrideTexture: Set<any>
  updateAnchorPosition: (n: any) => void
  anchorMesh: PinAnchorMesh
  onAnchorHover: () => void
  onAnchorUnhover: () => void
  selectedMesh: PinSelectedMesh
  constructor(e, t, i, s) {
    this.input = e
    this.layer = t
    this.commandBinder = i
    this.raycaster = s
    this.container = new THREE.Object3D()
    this.floorIdToContainer = new Map()
    this.hexColorToColor = new Map()
    this.bindings = []
    this.anchor = null
    this.selected = null
    this.haloEasing = ae.tf(0.5, 0.52, 0, 1.98)
    this.pinsWithOverrideTexture = new Set()
    this.updateAnchorPosition = (() => {
      const e = new THREE.Vector3()
      const t = new THREE.Vector3()
      const i = new THREE.Vector3()
      return (n: matterItemData) => {
        const s = this.anchorMesh
        if (!s) return
        const r = this.raycaster.picking
        e.copy(n.stemNormal).normalize()
        t.copy(e).multiplyScalar(0.2).add(n.anchorPosition)
        i.copy(e).multiplyScalar(-1)
        const o = r.pick(t, i, e => e instanceof RoomMesh)
        if (o) {
          const e = Math.min(0.2, o.distance)
          s.position.copy(t).add(i.multiplyScalar(0.999 * e))
        } else s.position.copy(t).add(i.multiplyScalar(0.999 * n.stemLength))
        s.lookAt(t)
      }
    })()
    this.onAnchorHover = () => {
      this.commandBinder.issueCommand(new SetMouseCursorCommand(cursorEnum.GRAB)), this.commandBinder.issueCommand(new DisableCursorMeshCommand(!0))
    }
    this.onAnchorUnhover = () => {
      this.commandBinder.issueCommand(new SetMouseCursorCommand(null)), this.commandBinder.issueCommand(new DisableCursorMeshCommand(!1))
    }
  }
  init() {
    this.container.name = "PinContainer"
    this.container.layers.mask = this.layer.mask
    this.anchorMesh = new PinAnchorMesh(this.layer)
    this.anchorMesh.name = "Anchor Mesh"
    this.container.add(this.anchorMesh)
    this.selectedMesh = new PinSelectedMesh(this.layer)
    this.selectedMesh.name = "Selected Mesh"
    this.container.add(this.selectedMesh)
  }
  dispose() {
    this.container.parent && this.container.parent.remove(this.container)
    for (const e of [this.anchorMesh, this.selectedMesh]) e && e.parent && e.parent.remove(e)
  }
  activate(e: EngineContext) {
    if (!this.bindings.length) {
      this.bindings.push(
        this.input.registerMeshHandler(HoverEvent, Comparator.isType(PinAnchorMesh), this.onAnchorHover),
        this.input.registerMeshHandler(UnhoverEvent, Comparator.isType(PinAnchorMesh), this.onAnchorUnhover)
      )
    }
  }
  deactivate(e) {
    for (const e of this.bindings) e.cancel()
    this.bindings.length = 0
  }
  pinHeadTransform(e?: any): THREE.Matrix4 {
    return new THREE.Matrix4()
  }
  render(e) {
    if (!this.selected) return
    const { animation, hideWhenDoneAnimating } = this.selected
    const s = this.selectedMesh
    if (s && s.visible && animation) {
      const e: THREE.Matrix4 = this.pinHeadTransform(this.selected.id)
      const r = new THREE.Vector3()
      const o = new THREE.Quaternion()
      const a = new THREE.Vector3()
      e.decompose(r, o, a)
      a.multiplyScalar(animation.getUpdatedValue())
      hideWhenDoneAnimating && !animation.isAnimating ? ((s.visible = !1), (this.selected = null)) : (s.position.copy(r), s.quaternion.copy(o), s.scale.copy(a))
    }
  }

  updatePin(e: string, t: string, i: matterItemData, n, s, r) {
    this.anchorMesh && this.anchorMesh.visible && this.anchor && this.anchor.pinType === t && this.anchor.id === e && this.updateAnchorPosition(i)
  }
  removePin(e) {
    this.selected && this.selected.id === e && this.clearSelected()
  }
  removePinsByType(e) {
    this.removePinsByPredicate(t => t === e)
  }
  removePinsByPredicate(e) {}
  setPinVisible(e, t) {}
  setPinColorVariant(e, t) {}
  setPinColorVariants(e, t) {}
  setPinColorVariantByType(e, t, i) {}
  setPinOpacity(e, t) {}
  setPinOpacityByType(e, t, i) {}
  fadePinOpacity(e, t) {}
  fadePinOpacityByType(e, t, i = []) {}
  setPinRenderOverrides(e, t, i) {
    t ? (this.pinsWithOverrideTexture.add(e), this.selected && this.selected.id === e && this.clearSelected()) : this.pinsWithOverrideTexture.delete(e)
  }
  setFloorsHidden(e) {
    this.floorIdToContainer.forEach((t, i) => {
      t.visible = !e(i)
    })
  }
  setPinTypeVisible(e, t) {
    this.floorIdToContainer.forEach(i => {
      i.userData.typeContainers[e].visible = t
    })
  }
  showAnchorMesh(pinType: string, id: string, i: matterItemData) {
    this.anchor = { pinType, id }
    this.anchorMesh && !this.anchorMesh.visible && ((this.anchorMesh.visible = !0), this.input.registerMesh(this.anchorMesh, !1))
    this.updateAnchorPosition(i)
  }
  hideAnchorMesh() {
    this.anchorMesh && this.anchorMesh.visible && ((this.anchorMesh.visible = !1), this.input.unregisterMesh(this.anchorMesh))
  }
  showSelectedMesh(pinType: string, id: string) {
    const i = this.selected && this.selected.pinType === pinType && this.selected.id === id
    if (!(this.selected && i) && !this.pinsWithOverrideTexture.has(id)) {
      this.selected = {
        pinType,
        id,
        hideWhenDoneAnimating: !1,
        animation: new Animation({
          startValue: 10,
          endValue: 14,
          duration: 300,
          easingFunction: this.haloEasing
        })
      }
      this.selectedMesh.visible = !0
    }
  }
  hideSelectedMesh() {
    this.selected &&
      ((this.selected.animation = new Animation({
        startValue: 14,
        endValue: 10,
        duration: 300
      })),
      (this.selected.hideWhenDoneAnimating = !0))
  }
  getFloorContainer(e: string) {
    let t = this.floorIdToContainer.get(e)
    if (t) return t
    t = new THREE.Object3D()
    t.name = "Floor " + e
    t.userData.typeContainers = {}
    t.layers.mask = this.layer.mask
    for (const e of Object.values(pinTypeEnum)) {
      const i = new THREE.Object3D()
      i.name = e
      i.layers.mask = this.layer.mask
      t.add(i)
      t.userData.typeContainers[e] = i
    }
    this.container.add(t)
    this.floorIdToContainer.set(e, t)
    t.userData.floorId = e

    return t
  }
  getColor(e: string) {
    let t = this.hexColorToColor.get(e)
    if (t) return t
    const i = new THREE.Color(e)
    const s = { h: 0, s: 0, l: 0 }
    i.getHSL(s)
    t = {
      baseColor: i,
      hoverColor: new THREE.Color().setHSL(s.h, s.s, 0.8 * s.l),
      dimmedColor: new THREE.Color().setHSL(s.h, 0.5 * s.s, s.l)
    }
    this.hexColorToColor.set(e, t)
    return t
  }
  getViewportScale(e) {
    return Math.sqrt(Math.min(e.width, e.height) / configConst.PlaneBufferGeometryConfg.pinHeadMesh.scale.baseViewportSize)
  }
  clearSelected() {
    this.selectedMesh.visible = !1
    this.selected = null
  }
}
